package cc.jq1024.middleware.encrypt.advice;


import cc.jq1024.middleware.encrypt.annotations.Decrypt;
import cc.jq1024.middleware.encrypt.annotations.DecryptBody;
import cc.jq1024.middleware.encrypt.annotations.DecryptIgnore;
import cc.jq1024.middleware.encrypt.config.EncryptProperties;
import cc.jq1024.middleware.encrypt.domain.entity.DecryptAnnotationInfoBean;
import cc.jq1024.middleware.encrypt.domain.entity.DecryptHttpInputMessage;
import cc.jq1024.middleware.encrypt.domain.valobj.DecryptMethod;
import cc.jq1024.middleware.encrypt.domain.valobj.EncryptKey;
import cc.jq1024.middleware.encrypt.exception.*;
import cc.jq1024.middleware.encrypt.util.AES;
import cc.jq1024.middleware.encrypt.util.RSA;
import cc.jq1024.middleware.encrypt.util.StringUtils;
import com.alibaba.fastjson2.JSONObject;
import org.apache.commons.io.IOUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import javax.el.MethodNotFoundException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

/**
 * 响应拦截器
 * @author li--jiaqiang
 */
@EnableConfigurationProperties(EncryptProperties.class) // 启用EncryptProperties类的配置属性。
@ControllerAdvice // 标记这个类作为控制器切面，允许它为控制器的方法提供跨切面的功能
public class DecryptRequestAdvice implements RequestBodyAdvice {

    private final EncryptProperties encryptProperties;

    public DecryptRequestAdvice(EncryptProperties encryptProperties) {
        this.encryptProperties = encryptProperties;
    }

    /**
     * 决定是否对给定的方法参数应用此建议。
     * 检查是否有Decrypt、DecryptBody注解，并且没有DecryptIgnore注解。
     * @return true: 进行解密， false:不进行解密
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 如果添加了 @DecryptIgnore 注解，表示不支持解密，返回false
        boolean ignore = methodParameter.hasMethodAnnotation(DecryptIgnore.class);
        if (ignore) {
            return false;
        }
        // 获取 methodParameter 所在类上的所有注解
        Annotation[] annotations = methodParameter.getDeclaringClass().getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            // 看是否包含 @Decrypt 注解
            if (annotation instanceof Decrypt)
                return true;
        }
        // 从方法上查找，是否存在 @Decrypt or @DecryptBody
        return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasMethodAnnotation(DecryptBody.class);
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    /**
     * 读取HTTP请求体之前被调用
     * @param inputMessage 要读取的HTTP请求体
     * @param parameter 当前处理的方法的参数信息
     * @param targetType 目标类型，即请求体的预期类型
     * @param converterType 用于转换请求体的转换器类型
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        // 保存 aesKey, 下放给响应拦截器
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String method = request.getMethod();

        // 忽略 get, options 请求
        if (method.equalsIgnoreCase("GET") || method.equalsIgnoreCase("OPTIONS")) {
            return inputMessage;
        }

        // 指定字符编码, 将请求体的内容读取为字符串
        String bodyString = IOUtils.toString(inputMessage.getBody(), encryptProperties.getEncoding());
        JSONObject jsonObject = JSONObject.parseObject(bodyString, JSONObject.class);
        String content = jsonObject.getString(EncryptKey.CONTENT_KEY.getValue());
        String key = jsonObject.getString(EncryptKey.SECURITY_KEY.getValue());
        // 记录解密之后的字符串
        String decryptBody = null;
        // 判断方法上是否有加密注解( @DecryptBody or @Decrypt)
        try {
            DecryptAnnotationInfoBean annotation = this.getMethodAnnotation(parameter, key);
            if (annotation != null){
                // 进行解密
                decryptBody = handleDecrypt(content, annotation);
            } else {
                // 方法上没有加密注解( @DecryptBody or @Decrypt), 从类上获取@Decrypt
                annotation = this.getClassAnnotation(parameter.getDeclaringClass(), key);
                if(annotation != null){
                    // 进行解密
                    decryptBody = handleDecrypt(content, annotation);
                }
            }
            assert annotation != null;


            request.setAttribute(EncryptKey.SECURITY_KEY.getValue(), annotation.getKey());

        } catch (EDException e) {
            throw new RuntimeException(e.getCause());
        }
        InputStream inputStream = IOUtils.toInputStream(decryptBody, encryptProperties.getEncoding());
        // 返回解密后的数据
        return new DecryptHttpInputMessage(inputStream, inputMessage.getHeaders());
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    /**
     * 从方法上获取 @Decrypt or @DecryptBody 注解信息,并包装对象
     */
    private DecryptAnnotationInfoBean getMethodAnnotation(MethodParameter methodParameter, String aesKey) throws AlgorithmException, DecryptException {
        // 添加 @DecryptBody 注解的处理方式
        if (methodParameter.hasMethodAnnotation(DecryptBody.class)){
            DecryptBody decryptBody = methodParameter.getMethodAnnotation(DecryptBody.class);
            if (decryptBody != null){
                if (decryptBody.method() == DecryptMethod.AesWithRsa) {
                    // 解密 aesKey
                    aesKey = RSA.decrypt(aesKey, RSA.getPublicKey(encryptProperties.getPublickey()));
                }
                // 拿到解密key
                String key = StringUtils.getOrDefault(decryptBody.key(), aesKey);
                return new DecryptAnnotationInfoBean(key, decryptBody.method());
            }
        }
        // 添加 @Decrypt 注解的处理方式
        if (methodParameter.hasMethodAnnotation(Decrypt.class)){
            Decrypt decrypt = methodParameter.getMethodAnnotation(Decrypt.class);
            if (decrypt != null){
                if (decrypt.method() == DecryptMethod.AesWithRsa) {
                    // 解密 aesKey
                    aesKey = RSA.decrypt(aesKey, RSA.getPublicKey(encryptProperties.getPublickey()));
                }
                String key = StringUtils.getOrDefault(decrypt.key(), aesKey);
                return new DecryptAnnotationInfoBean(key, decrypt.method());
            }
        }
        //两个注解都没找到，返回 null
        return null;
    }

    /**
     * 从类上获取 @Decrypt 注解信息,并包装对象
     * 因为 @DecryptBody 注解不能加在方法上，所以只有 @Decrypt
     */
    private DecryptAnnotationInfoBean getClassAnnotation(Class<?> clazz, String aesKey) throws AlgorithmException, DecryptException {
        Annotation[] annotations = clazz.getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof Decrypt) {
                Decrypt decrypt = (Decrypt) annotation;
                if (decrypt.method() == DecryptMethod.AesWithRsa) {
                    // 解密 aesKey
                    aesKey = RSA.decrypt(aesKey, RSA.getPublicKey(encryptProperties.getPublickey()));
                }
                String key = StringUtils.getOrDefault(decrypt.key(), aesKey);
                return new DecryptAnnotationInfoBean(key, decrypt.method());
            }
        }
        return null;
    }

    /**
     * @param string 解密字符串
     * @param infoBean 解密注解信息
     * @return 解密结果
     */
    private String handleDecrypt(String string, DecryptAnnotationInfoBean infoBean) throws DecryptException, AlgorithmException, EncryptException {
        if(string == null || string.isEmpty()){
            throw new DecryptNullBodyException(new Exception("content is null!"));
        }
        DecryptMethod method = infoBean.getDecryptMethod();
        if(method == null) {
            throw new DecryptMethodNotFoundException(new Exception(" 'method' is null!"));
        }
        // 解密
        if(method == DecryptMethod.AES) {
            String key = infoBean.getKey();
            if (StringUtils.isBankOrEmpty(key)){
                throw new DecryptKeyNotFoundException(new Exception(" 'key' is null or empty!"));
            }
            return AES.decrypt(string, key);
        }
        if (method == DecryptMethod.RSA) {
            return RSA.decrypt(string, RSA.getPublicKey(encryptProperties.getPublickey()));
        }
        if (method == DecryptMethod.AesWithRsa) {
            String aesKey = infoBean.getKey();
            // 使用明文的 aesKey 解密数据
            return AES.decrypt(string, aesKey);
        }
        throw new DecryptNotSupportedException(new MethodNotFoundException("DecryptMethod not supported! "+ method));
    }

}
